import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Collections;
import java.io.*;

/**
 * This class will create a timeDaysLink data collection. Not need to execute
 * every time because data collection will be acting like database.
 * */
public class TimePeriodGrouping {
	private Connection conn_mainDB;
	private ArrayList<String> linkCollection;
	private ArrayList<String> linkCollectionUnique = new ArrayList<String>();
	private ArrayList<Integer> linkCollectionUniqueInt = new ArrayList<Integer>();

	private File f;
	private ReadFile read;

	double[][][] cubeAgg;
	double[][][] cubeAvg;
	private ArrayList<ClassifyTimePerDay> mondayTime;
	private ArrayList<ClassifyTimePerDay> tuesdayTime;
	private ArrayList<ClassifyTimePerDay> wednesdayTime;
	private ArrayList<ClassifyTimePerDay> thursdayTime;
	private ArrayList<ClassifyTimePerDay> fridayTime;
	private ArrayList<ClassifyTimePerDay> saturdayTime;
	private ArrayList<ClassifyTimePerDay> sundayTime;
	private ArrayList<LinkCharateristic> linkObj = new ArrayList<LinkCharateristic>();

	// private ClassifyTimePerDay timePerDay;

	public TimePeriodGrouping() {
		try {
			conn_mainDB = DriverManager.getConnection(MainMap.mainDB, "fang",
					"root");

		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	public ArrayList<LinkCharateristic> setUpLinkID(String output_filename)
			throws IOException {
		Util.writeLog("1. Gather the unique links");
		f = new File(output_filename);
		ArrayList<LinkCharateristic> link = new ArrayList<LinkCharateristic>();
		SourceGPS s = new SourceGPS(conn_mainDB);
		LinkedList<GPSpnt> pntList = s.getBatchWithLink();

		int pointSize = pntList.size();
		Util.writeLog("\t - Check a file " + Config.LINKID_PATH
				+ " is exist or not");

		if (!f.exists()) {
			Util.writeLog("\t - It is not exist");
			getUniqueLink(pntList, pointSize, Config.LINKID_PATH);
		} else if (f.exists() && f.length() > 0) {
			Util.writeLog("\t - It is exist");
			read = new ReadFile(Config.LINKID_PATH);
			linkCollectionUnique = read.readText();
		}

		if (pointSize <= 0) {
			Util.writeLog(s + " ==> no data to process...");
		} else {
			Util.writeLog("\n2. Unique the linkID and write into file text");
			link = classifyTimeInterval(linkCollectionUnique, pntList);
		}
		return link;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Classify time by days and linkID
	// Use source id to classify process because in linkObj-> there are 7 obj
	// per link (7 days)
	// but we cannot know what the first and last GPS that need to identify in
	// specific time period
	// source id will help us to identify GPS
	public ArrayList<LinkCharateristic> classifyTimeInterval(
			ArrayList<String> linkUnique, LinkedList<GPSpnt> pntList)
			throws IOException {
		ClassifyTimePerDay timePerDay;
		linkObj = new ArrayList<LinkCharateristic>();
		for (String linkEle : linkUnique) {// for#1

			mondayTime = new ArrayList<ClassifyTimePerDay>();
			tuesdayTime = new ArrayList<ClassifyTimePerDay>();
			wednesdayTime = new ArrayList<ClassifyTimePerDay>();
			thursdayTime = new ArrayList<ClassifyTimePerDay>();
			fridayTime = new ArrayList<ClassifyTimePerDay>();
			saturdayTime = new ArrayList<ClassifyTimePerDay>();
			sundayTime = new ArrayList<ClassifyTimePerDay>();

			// Declare new object every time when next link is proceeded
			for (GPSpnt pntEle : pntList) {// for#2
				String linkIDEle = pntEle.linkID.toString();

				if (linkEle.equalsIgnoreCase(linkIDEle)) { // If#1
					// System.out.println(linkEle + " == " + linkIDEle);

					String[] timestamp_temp = Util
							.splitWithSpace2A(pntEle.rectimeStr);
					String[] timestamp = Util
							.splitWithPlot2A(timestamp_temp[1]);
					String day = pntEle.dayName.toLowerCase();
					String row_id = String.valueOf(pntEle.row_id);
					String link_id = String.valueOf(linkEle);
					String source_id = pntEle.source_id;

					// System.out.println(day+" "+link_id + " >> " +
					// timestamp[0]+" >> "+source_id);

					if (day.equalsIgnoreCase(Config.DAYS[0])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						mondayTime.add(timePerDay);

					} else if (day.equalsIgnoreCase(Config.DAYS[1])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						tuesdayTime.add(timePerDay);
					} else if (day.equalsIgnoreCase(Config.DAYS[2])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						wednesdayTime.add(timePerDay);

					} else if (day.equalsIgnoreCase(Config.DAYS[3])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						thursdayTime.add(timePerDay);
					} else if (day.equalsIgnoreCase(Config.DAYS[4])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						fridayTime.add(timePerDay);
					} else if (day.equalsIgnoreCase(Config.DAYS[5])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timePerDay.setSource_id(source_id);
						timeClassSetting(timePerDay, timestamp[0]);

						saturdayTime.add(timePerDay);
					} else if (day.equalsIgnoreCase(Config.DAYS[6])) {
						timePerDay = new ClassifyTimePerDay(row_id, link_id,
								timestamp[0]);
						timeClassSetting(timePerDay, timestamp[0]);
						timePerDay.setSource_id(source_id);
						sundayTime.add(timePerDay);
					}
				}// End if#1
			}// End for#2

			// System.out.println("\t"+linkEle);
			LinkCharateristic lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[0].toLowerCase());
			lc.setTimeInterval(mondayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[1].toLowerCase());
			lc.setTimeInterval(tuesdayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[2].toLowerCase());
			lc.setTimeInterval(wednesdayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[3].toLowerCase());
			lc.setTimeInterval(thursdayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[4].toLowerCase());
			lc.setTimeInterval(fridayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[5].toLowerCase());
			lc.setTimeInterval(saturdayTime);
			linkObj.add(lc);

			lc = new LinkCharateristic();
			lc.setLinkID(linkEle);
			lc.setDay(Config.DAYS[6].toLowerCase());
			lc.setTimeInterval(sundayTime);
			linkObj.add(lc);
			// releaseResource();
			
		}// End for#1

		Util.writeLog("2.1 Classify data into (linkID -> row_id, time_stamp, days) group by days");
		Util.writeLog("2.2 Set time class (30 interval) to all GPS data");
		return linkObj;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public void printResult() throws IOException {
		FileWriter output = new FileWriter("test.txt");
		BufferedWriter out = new BufferedWriter(output);
		int i = 0, j = 0;
		for (LinkCharateristic lc : linkObj) {
			i++;
			for (ClassifyTimePerDay ct : lc.getTimeInterval()) {
				j++;
				System.out.println(ct.toString());
				out.write(ct.toString());
				out.newLine();
			}
		}
		out.close();
		System.out.println(i + " * " + j);
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public void timeClassSetting(ClassifyTimePerDay classifyTimePerDay,
			String timeActual) {
		String[] curr_time = Util.splitWithColon3A(timeActual);
		String hour = curr_time[0];
		if (hour.length() == 1) {
			hour = "0" + hour;
		}
		int minute = Integer.parseInt(curr_time[1]);

		if (minute < 30) {
			classifyTimePerDay.setTimeClass(hour + ":00:00");
		} else {
			classifyTimePerDay.setTimeClass(hour + ":30:00");
		}
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public double[][][] groupSourceIDperLink(double[][][] cube, GenCubeMatrix gen)
			throws IOException {

		FileWriter output2 = new FileWriter(Config.SOURCECLASSTIME_PATH);
		BufferedWriter out2 = new BufferedWriter(output2);

		cubeAgg = new double[gen.getDaysTable().size()][gen.getTimesTable()
				.size()][gen.getLinkIDArrUnique().size()];
		// printResult();
		Util.writeLog("4. Set up cube");
		for (LinkCharateristic lchar : linkObj) {

			if (lchar.getTimeInterval().size() > 0) {
				LinkedList<ClassifyTimePerDay> source_group = new LinkedList<ClassifyTimePerDay>();
				ArrayList<String> source_id_arr = new ArrayList<String>();
				ArrayList<String> source_id_arr_tmp = new ArrayList<String>();

				ArrayList<String> time_class_arr = new ArrayList<String>();
				ArrayList<String> time_class_tmp = new ArrayList<String>();

				for (int i = 0; i < lchar.getTimeInterval().size(); i++) {
					source_id_arr.add(lchar.getTimeInterval().get(i)
							.getSource_id());

					time_class_arr.add(lchar.getTimeInterval().get(i)
							.getTimeClass());
				}

				Collections.sort(source_id_arr);
				Collections.sort(time_class_arr);

				for (String source : GetUniqueValues(source_id_arr)) {
					source_id_arr_tmp.add(source);
				}

				for (String timeClass : GetUniqueValues(time_class_arr)) {
					time_class_tmp.add(timeClass);
				}

				Collections.sort(source_id_arr_tmp);
				Collections.sort(time_class_tmp);

				ArrayList<String> tmp = time_class_tmp;

				for (String sid : source_id_arr_tmp) {
					for (int k = 0; k < tmp.size(); k++) {

						for (int i = 0; i < lchar.getTimeInterval().size(); i++) {

							if (lchar.getTimeInterval().get(i).getSource_id()
									.equalsIgnoreCase(sid)) {
								if (lchar.getTimeInterval().get(i)
										.getTimeClass()
										.equalsIgnoreCase(tmp.get(k))) {
									source_group.add(lchar.getTimeInterval()
											.get(i));

								}
							}
						}
					}
				}
				// char c = (char) System.in.read();
				int indexOfDay = gen.getDaysTable().indexOf(lchar.getDay());

				// =====[Test Result]============
				// Write to SourceClassTime.txt
				out2.write(lchar.getDay() + " = " + indexOfDay);
				out2.newLine();
				out2.write(source_group.size() + " -- ");
				out2.newLine();
				for (int i = 0; i < source_group.size(); i++) {
					out2.write("" + source_group.get(i));
					out2.newLine();
				}
				// ==============================

				cube = addToCube(source_group, cube, indexOfDay, gen,
						source_id_arr_tmp, time_class_tmp);
			}
		}
		out2.close();
		Util.writeLog("4.1 Cube is filled by historical data");
		printCube(Config.CUBE_PATH, cube, gen);
		printCube(Config.CUBEAGG_PATH, cubeAgg, gen);

		cubeAvg = calculateAverage(cube, cubeAgg);
		printCube(Config.CUBEAVG_PATH, cubeAvg, gen);
		Util.writeLog("4.2 Cube is completely filled by average of historical data");
		return cubeAvg;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public double[][][] calculateAverage(double[][][] cube, double[][][] cubeAgg) {
		cubeAvg = new double[cube.length][cube[0].length][cube[0][0].length];
		for (int i = 0; i < cube.length; i++) {
			for (int j = 0; j < cube[0].length; j++) {
				for (int k = 0; k < cube[0][0].length; k++) {
					if (cubeAgg[i][j][k] > 0)
						// Unit : seconds
						cubeAvg[i][j][k] = (cube[i][j][k] / cubeAgg[i][j][k]);
					else
						cubeAvg[i][j][k] = 0;
				}
			}
		}
		return cubeAvg;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public double[][][] addToCube(LinkedList<ClassifyTimePerDay> source_group,
			double[][][] cube, int indexOfDay, GenCubeMatrix gen,
			ArrayList<String> source_id_arr, ArrayList<String> time_class_tmp)
			throws IOException {
		double[][][] updated_cube = new double[gen.getDaysTable().size()][gen
				.getTimesTable().size()][gen.getLinkIDArrUnique().size()];
		updated_cube = cube;
		ArrayList<ClassifyTimePerDay> source_group_sourceID;
		ArrayList<ClassifyTimePerDay> source_group_time;

		int chk = 0;
		for (int i = 0; i < source_id_arr.size(); i++) {
			source_group_sourceID = new ArrayList<ClassifyTimePerDay>();
			// Group by source id
			for (int j = chk; j < source_group.size(); j++) {
				if (source_group.get(j).getSource_id()
						.equalsIgnoreCase(source_id_arr.get(i))) {
					source_group_sourceID.add(source_group.get(j));
				} else {
					chk = j;
					break;
				}
			}

			if (source_group_sourceID.size() == 1) {

				updated_cube[indexOfDay][gen.getTimesTable().indexOf(
						source_group_sourceID.get(0).getTimeClass())][gen
						.getLinkIDArrUnique().indexOf(
								source_group_sourceID.get(0).getLink_id())] = updated_cube[indexOfDay][gen
						.getTimesTable().indexOf(
								source_group_sourceID.get(0).getTimeClass())][gen
						.getLinkIDArrUnique().indexOf(
								source_group_sourceID.get(0).getLink_id())] + 1;

				cubeAgg[indexOfDay][gen.getTimesTable().indexOf(
						time_class_tmp.get(0))][gen.getLinkIDArrUnique()
						.indexOf(source_group.get(0).getLink_id())]

				= cubeAgg[indexOfDay][gen.getTimesTable().indexOf(
						time_class_tmp.get(0))][gen.getLinkIDArrUnique()
						.indexOf(source_group.get(0).getLink_id())] + 1;
			} else {

				for (int j = 0; j < time_class_tmp.size(); j++) {
					source_group_time = new ArrayList<ClassifyTimePerDay>();
					// Group by timeClass
					for (int j2 = 0; j2 < source_group_sourceID.size(); j2++) {

						if (time_class_tmp.get(j).equalsIgnoreCase(
								source_group_sourceID.get(j2).getTimeClass())) {
							source_group_time
									.add(source_group_sourceID.get(j2));
						}
					}

					if (source_group_time.size() > 0) {
						// System.out.println("START : "
						// + source_group_time.get(0));
						String time_start = source_group_time.get(0)
								.getTimestamp();
						// System.out.println("STOP : "
						// + source_group_time.get(source_group_time
						// .size() - 1));
						String time_end = source_group_time.get(
								source_group_time.size() - 1).getTimestamp();
						int init = convertToSeconds(time_start);
						int sub = convertToSeconds(time_end);

						int diff = Math.abs(sub - init);

						// System.out.println(gen.getTimesTable()
						// +" AT "+time_class_tmp.get(j));
						// System.out.println(indexOfDay
						// + ","
						// + gen.getTimesTable().indexOf(
						// time_class_tmp.get(j))
						// + ","
						// + gen.getLinkIDArrUnique().indexOf(
						// source_group.get(0).getLink_id()));

						updated_cube[indexOfDay][gen.getTimesTable().indexOf(
								time_class_tmp.get(j))][gen
								.getLinkIDArrUnique().indexOf(
										source_group.get(0).getLink_id())]

						= updated_cube[indexOfDay][gen.getTimesTable().indexOf(
								time_class_tmp.get(j))][gen
								.getLinkIDArrUnique().indexOf(
										source_group.get(0).getLink_id())]
								+ diff;

						cubeAgg[indexOfDay][gen.getTimesTable().indexOf(
								time_class_tmp.get(j))][gen
								.getLinkIDArrUnique().indexOf(
										source_group.get(0).getLink_id())]

						= cubeAgg[indexOfDay][gen.getTimesTable().indexOf(
								time_class_tmp.get(j))][gen
								.getLinkIDArrUnique().indexOf(
										source_group.get(0).getLink_id())] + 1;
					}
				}
			}
		}
		// char c = (char) System.in.read();
		return updated_cube;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public int convertToSeconds(String objFirst) {
		int timeInSecs = 0;
		String[] tmp = objFirst.split(":");
		if (tmp.length <= 3) {
			int hour = Integer.parseInt(tmp[0]);
			int min = Integer.parseInt(tmp[1]);
			int sec = Integer.parseInt(tmp[2]);

			hour = hour * Util.getHOUR_IN_SEC();
			min = min * Util.getMIN_IN_SEC();

			timeInSecs = hour + min + sec;
		}

		return timeInSecs;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public void releaseResource() {
		mondayTime.clear();
		tuesdayTime.clear();
		wednesdayTime.clear();
		thursdayTime.clear();
		fridayTime.clear();
		saturdayTime.clear();
		sundayTime.clear();
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	public void getUniqueLink(LinkedList<GPSpnt> pntList, int pointSize,
			String output_filename) throws IOException {
		linkCollection = new ArrayList<String>();

		for (int i = 0; i < pointSize; i++) {
			linkCollection.add(pntList.get(i).linkID.toString());
		}
		Collections.sort(linkCollection);

		for (String link_id : GetUniqueValues(linkCollection)) {
			linkCollectionUniqueInt.add(Integer.parseInt(link_id));

		}
		Collections.sort(linkCollectionUniqueInt);
		for (Integer lid : linkCollectionUniqueInt) {
			linkCollectionUnique.add(String.valueOf(lid));
		}
		// Write to a text file
		WriteProcess(linkCollectionUnique, output_filename);
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	public void WriteProcess(ArrayList<String> linkCollectionUnique,
			String output_filename) throws IOException {
		new WriteFile(linkCollectionUnique, output_filename);
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	public ArrayList<String> GetUniqueValues(Collection<String> values) {
		return new ArrayList<String>(new HashSet<String>(values));
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	public String identifyDaysOfWeek(String dateStr) {
		String dayName = "";
		String[] dateString = dateStr.split("-");
		Calendar day = Calendar.getInstance();
		day.set(Calendar.YEAR, Integer.parseInt(dateString[0]));
		day.set(Calendar.MONTH, Integer.parseInt(dateString[1]) - 1);
		day.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString[2]));

		int weekday = day.get(Calendar.DAY_OF_WEEK);

		// System.out.println(day.getTime());
		// System.out.println(weekday + " : " + dateString[0] + "-"+
		// dateString[1] + "-" + dateString[2]);

		switch (weekday) {
		case 1:
			dayName = "Sunday";
			break;
		case 2:
			dayName = "Monday";
			break;
		case 3:
			dayName = "Tuesday";
			break;
		case 4:
			dayName = "Wednesday";
			break;
		case 5:
			dayName = "Thursday";
			break;
		case 6:
			dayName = "Friday";
			break;
		case 7:
			dayName = "Saturday";
			break;
		default:
			dayName = "";
			break;
		}

		return dayName;
	}

	// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	public void printCube(String cubePath, double[][][] cube, GenCubeMatrix gen)
			throws IOException {
		FileWriter output = new FileWriter(cubePath);
		BufferedWriter out = new BufferedWriter(output);

		int sizeOfDay = cube.length;
		int sizeOfTime = cube[0].length;
		int sizeOfLink = cube[0][0].length;
		out.write("DAY\t\tTIME\t\tLINK");
		out.newLine();
		for (int i = 0; i < sizeOfDay; i++) {
			for (int j = 0; j < sizeOfTime; j++) {
				for (int k = 0; k < sizeOfLink; k++) {
					if (cube[i][j][k] > 0) {
						out.write(gen.getDaysTable().get(i) + "\t\t");
						out.write(gen.getTimesTable().get(j) + "\t\t");
						out.write(gen.getLinkIDArrUnique().get(k) + "\t\t");
						out.write(cube[i][j][k] + "");
						out.newLine();
					}
				}
			}
		}
		out.close();
	}

	
}
